/*
 * PathNormal.java
 *
 * Created on November 18, 2007, 11:50 AM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.wave;

import com.twolattes.json.Entity;
import com.twolattes.json.Value;
import hextd.*;
import hextd.UI.ScaledCanvas;
import hextd.core.Context;
import hextd.core.ContextNormal;
import hextd.core.GameGrid;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RadialGradientPaint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author hylke
 */
@Entity
public class PathNormal implements Path {

	private Context context;
	private ContextNormal cn;
	private int[] stepsX;
	private int[] stepsY;
	private boolean leftToRight = true;
	private static Color blipColor = new Color(1.0f, 0.7f, 0.7f, 1f);
	private static Color blipBackColor = new Color(1.0f, 0.7f, 0.7f, 0f);
	private static Color lineColor = new Color(0.7f, 0.7f, 0.7f, 0.5f);
	private Stroke stroke;
	private Shape blipShape;
	private Paint blipFill;
	private float blipSize;
	private BufferedImage[] blipImage;
	private int[] blipStep;
	AffineTransform blipTranslate;
	private int lightTimes = 20;
	private int darkTimes = 60;
	private boolean selected = false;
	private boolean finalised = false;
	//
	@Value(optional = true)
	public int target = 0;
	@Value(optional = true)
	public int owner = -1;
	@Value(optional = true)
	public float baseDelay = 0.0f;
	@Value
	public List<List<Integer>> waypoints;

	public PathNormal() {
		init();
	}

	public PathNormal(float baseDelay, int targetUser, int owner) {
		this.baseDelay = baseDelay;
		this.target = targetUser;
		this.owner = owner;
		this.waypoints = new ArrayList<List<Integer>>();
		init();
	}

	private void init() {
		this.blipTranslate = new AffineTransform();
	}

	@Override
	public void setContext(Context c) {
		this.context = c;
		if (context instanceof ContextNormal) {
			cn = (ContextNormal) context;
			this.leftToRight = cn.getGameGrid().isLeftToRight();
		}
	}

	@Override
	public float getBaseDelay() {
		return this.baseDelay;
	}

	public void setBaseDelay(float d) {
		this.baseDelay = d;
	}

	public void addStep(int x, int y) {
		ArrayList<Integer> step = new ArrayList<Integer>();
		step.add(x);
		step.add(y);
		waypoints.add(step);
	}

	@Override
	public int length() {
		return this.stepsX.length;
	}

	@Override
	public int getTargetUser() {
		return this.target;
	}

	public void setTargetUser(int user) {
		target = user;
	}

	public void setOwner(int user) {
		owner = user;
	}

	@Override
	public int getOwner() {
		return owner;
	}

	@Override
	public int[] getStepHexCoords(int step) {
		int[] retval = new int[2];
		retval[0] = this.stepsX[step];
		retval[1] = this.stepsY[step];
		return retval;
	}

	public float[] getStepPaint(int step) {
		float[] loc = this.getStep(step);
		ScaledCanvas sc = cn.getScaledCanvas();
		loc[0] *= sc.getPaintScale();
		loc[1] *= sc.getPaintScale();
		loc[0] += sc.getOffsetX();
		loc[1] += sc.getOffsetY();
		return loc;
	}

	@Override
	public float[] getStep(int step) {
		if (this.stepsX.length == 0) {
			float[] retval = {0f, 0f};
			return retval;
		} else {
			if (step < 0) {
				return this.getPreStep();
			}
			if (step >= this.stepsX.length) {
				return this.getPostStep();
			}
			int x = stepsX[step];
			int y = stepsY[step];
			GameGrid grid = context.getGameGrid();
			if (x < 0 ||
					y < 0 ||
					x >= grid.getGridWidth() ||
					y >= grid.getGridHeight() ||
					grid.grid[x][y] == null) {
				float[] retval = {0f, 0f};
				return retval;
			}
			return grid.grid[x][y].getFloatLocation();
		}
	}

	private float[] getPreStep() {
		int dx = 0;
		int dy = 0;
		GameGrid grid = context.getGameGrid();
		float[] retVal = new float[2];
		if (stepsX[0] == 0) {
			dx = -2;
		}
		if (stepsX[0] == grid.getGridWidth() - 1 - (stepsY[0] & 1)) {
			dx = 2;
		}
		if (stepsY[0] == 0) {
			dy = -2;
		}
		if (stepsY[0] == grid.getGridHeight() - 1) {
			dy = 2;
		}
		retVal = getStep(0);
		retVal[0] += dx;
		retVal[1] += dy;
		return retVal;
	}

	private float[] getPostStep() {
		int count = this.stepsX.length - 1;
		int dx = 0;
		int dy = 0;
		GameGrid grid = context.getGameGrid();
		float[] retVal = new float[2];
		if (stepsX[count] == 0) {
			dx = -2;
		}
		if (stepsX[count] == grid.getGridWidth() - 1 - (stepsY[count] & 1)) {
			dx = 2;
		}
		if (stepsY[count] == 0) {
			dy = -2;
		}
		if (stepsY[count] == grid.getGridHeight() - 1) {
			dy = 2;
		}
		retVal = this.getStep(count);
		retVal[0] += dx;
		retVal[1] += dy;
		return retVal;
	}

	public void insertStep(int i, int x, int y) {
		ArrayList<Integer> step = new ArrayList<Integer>();
		step.add(x);
		step.add(y);
		waypoints.add(i, step);
		this.finalise();
	}

	public void updateStep(int i, int x, int y) {
		ArrayList<Integer> step = new ArrayList<Integer>();
		step.add(x);
		step.add(y);
		waypoints.remove(i);
		waypoints.add(i, step);
		this.finalise();
	}

	public void removeStep(int i) {
		waypoints.remove(i);
		this.finalise();
	}

	@Override
	public void finalise() {
		stepsX = new int[waypoints.size()];
		stepsY = new int[waypoints.size()];
		int i = 0;
		int[] from0, from1;
		if (leftToRight) {
			from0 = stepsX;
			from1 = stepsY;
		} else {
			from0 = stepsY;
			from1 = stepsX;
		}
		for (List<Integer> step : waypoints) {
			from0[i] = step.get(0);
			from1[i] = step.get(1);
			i++;
		}

		int blipCount = this.length();
		this.blipStep = new int[blipCount];

		for (i = 0; i < this.blipStep.length; i++) {
			this.blipStep[i] = lightTimes + darkTimes - 1 - i % (lightTimes + darkTimes);
		}
		finalised = true;
	}

	@Override
	public void paint(Graphics2D g2, int gameTime) {
		if (this.blipImage == null || this.stepsX.length == 0 || !finalised) {
			// Don't try to paint an empty path...
		} else {
			Stroke defaultStroke = g2.getStroke();

			if (selected) {

				Shape circle;
				g2.setColor(lineColor);
				g2.setStroke(this.stroke);
				GeneralPath poly1 = new GeneralPath(GeneralPath.WIND_EVEN_ODD, this.stepsX.length);
				float[] point = this.getStepPaint(-1);
				poly1.moveTo(point[0], point[1]);
				for (int index = 0; index <= this.stepsX.length; index++) {
					point = this.getStepPaint(index);
					poly1.lineTo(point[0], point[1]);
					circle = new Ellipse2D.Float(point[0] - this.blipSize / 2, point[1] - this.blipSize / 2, this.blipSize, this.blipSize);
					g2.draw(circle);
				}
				g2.draw(poly1);

			}

			for (int i = 0; i < this.blipStep.length; i++) {
				this.blipStep[i]++;
				if (this.blipStep[i] == lightTimes + darkTimes) {
					this.blipStep[i] = 0;
				}
				int image = this.blipStep[i];
				if (image > 0 && image < this.blipImage.length) {
					float[] point1 = this.getStepPaint(i);
					float x, y;
					x = point1[0] - this.blipSize / 2;
					y = point1[1] - this.blipSize / 2;
					g2.drawImage(this.blipImage[image], (int) x, (int) y, null);
				}
			}
			g2.setStroke(defaultStroke);

		}
	}

	@Override
	public void resetScale() {
		ScaledCanvas sc = cn.getScaledCanvas();

		if (this.blipImage != null) {
			for (int i = 0; i < this.blipImage.length; i++) {
				if (this.blipImage[i] != null) {
					this.blipImage[i].flush();
				}
			}
		} else {
			this.blipImage = new BufferedImage[this.lightTimes];
		}

		this.stroke = new BasicStroke(1.0f * sc.getStrokeScale(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);

		this.blipSize = Math.max(1f, sc.getPaintScale() / 4f);
		this.blipShape = new Ellipse2D.Float(0, 0, this.blipSize, this.blipSize);
		float[] dist = {0.0f, 1f};
		int num = this.blipImage.length;
		for (int i = 0; i < num; i++) {
			int trans = Math.max(0, 255 - i * 255 / num);
			Color[] colors = {new Color(blipColor.getRed(), blipColor.getGreen(), blipColor.getBlue(), trans), blipBackColor};
			this.blipFill = new RadialGradientPaint(this.blipSize / 2, this.blipSize / 2, this.blipSize / 2.1f, dist, colors);

			this.blipImage[i] = new BufferedImage(Math.round(this.blipSize), Math.round(this.blipSize), BufferedImage.TYPE_INT_ARGB_PRE);
			Graphics2D g2 = this.blipImage[i].createGraphics();
			g2.setPaint(blipFill);
			g2.fill(blipShape);
			g2.dispose();
		}
	}

	private void shiftUpDown(int to, int correction) {
		Integer x, y, shift;
		for (int i = 0; i < this.stepsY.length; i++) {
			//this.stepsY[i] += to;
			List<Integer> remove = waypoints.remove(i);
			x = remove.get(0);
			y = remove.get(1);

			shift = 0;
			boolean odd = ((y & 1) == 1);
			if (correction < 0) {
				odd = !odd;
			}
			if (odd) {
				shift = correction;
			}

			y += to;
			x += shift;
			List<Integer> add = new ArrayList<Integer>(2);
			add.add(x);
			add.add(y);
			waypoints.add(i, add);
		}
		this.finalise();
	}

	private void shiftLeftRight(int to) {
		Integer y;
		for (int i = 0; i < this.stepsX.length; i++) {
			List<Integer> get = waypoints.remove(i);
			List<Integer> add = new ArrayList<Integer>(2);
			add.add(get.get(0));
			add.add(get.get(1) + to);
			waypoints.add(i, add);
		}
		this.finalise();
	}

	public void shift(HexTD.Direction direction, int amount, int correction) {
		switch (direction) {
			case UP:
			case NORTH:
				this.shiftUpDown(-amount, correction);
				break;
			case DOWN:
			case SOUTH:
				this.shiftUpDown(amount, correction);
				break;
			case RIGHT:
			case EAST:
				this.shiftLeftRight(amount);
				break;
			case LEFT:
			case WEST:
				this.shiftLeftRight(-amount);
				break;
		}
	}

	@Override
	public boolean isSelected() {
		return selected;
	}

	@Override
	public void setSelected(boolean selected) {
		this.selected = selected;
	}
}
